using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Windows.Forms;
using System.Configuration;

namespace VSSDKAssist.Recipes.Common.Editors.Grid
{
    /// <summary/>
    public static class Validation
    {
        /// <summary>
        /// Executes the <see cref="ConfigurationValidatorAttribute"/> attributes on the
        /// data members binded to a DataGridView and adds error text labels to the cells
        /// </summary>
        /// <param name="grid">The grid to validate on</param>
        /// <param name="site">The site.</param>
        /// <param name="binding">The binding.</param>
        /// <returns>
        /// Returns <code>true</code> if the validation succeeded, <code>false</code> otherwise
        /// </returns>
        public static bool ExecuteConfigurationValidatorAttributes(DataGridView grid, ISite site, IBindingStrategy binding)
        {
            if(grid == null)
                throw new ArgumentNullException("grid");

            if(binding == null)
                throw new ArgumentNullException("binding");

            bool isValid = true;

            // Executes validators in each cell and add an ErrorText
            foreach(DataGridViewRow gridRow in grid.Rows)
            {
                object boundItem = binding.GetBoundItem(gridRow);
                if(boundItem == null)
                    continue;

                Type itemType = boundItem.GetType();
                foreach(DataGridViewCell gridCell in gridRow.Cells)
                {
                    isValid &= ExecuteConfigurationValidatorAttributes(gridCell, itemType, site);
                }
            }

            return isValid;
        }

        /// <summary>
        /// Executes the <see cref="ConfigurationValidatorAttribute"/> attributes on the
        /// data member binded to a DataGridViewCell and adds error text label to the cell
        /// </summary>
        /// <param name="gridCell">The cell to validate</param>
        /// <param name="itemType"></param>
        /// <param name="site"></param>
        /// <returns></returns>
        public static bool ExecuteConfigurationValidatorAttributes(DataGridViewCell gridCell, Type itemType, ISite site)
        {
            if(gridCell == null)
                throw new ArgumentNullException("gridCell");

            if(itemType == null)
                throw new ArgumentNullException("itemType");

            if(gridCell.OwningColumn.DataPropertyName == null)
                return true;

            gridCell.ErrorText = null;

            PropertyInfo itemProperty = itemType.GetProperty(gridCell.OwningColumn.DataPropertyName);
            if(itemProperty == null)
                return true;

            // Executes a type converter associated with the property
            TypeConverterAttribute converterAttribute = Attribute.GetCustomAttribute(itemProperty, typeof(TypeConverterAttribute)) as TypeConverterAttribute;
            if(converterAttribute != null)
            {
                Type converterType = Type.GetType(converterAttribute.ConverterTypeName, true);
                TypeConverter converter = (TypeConverter)Activator.CreateInstance(converterType);

                try
                {
                    gridCell.Value = converter.ConvertFrom(gridCell.Value);
                }
                catch(Exception ex)
                {
                    AppendErrorText(gridCell, ex.Message);
                    return false;
                }
            }

            bool isValid = true;

            foreach(ConfigurationValidatorAttribute validatorAttr in itemProperty.GetCustomAttributes(typeof(ConfigurationValidatorAttribute), true))
            {
                try
                {
                    ConfigurationValidatorBase validator = validatorAttr.ValidatorInstance;
                    IComponent validatorComponent = validator as IComponent;
                    if(validatorComponent != null)
                        validatorComponent.Site = site;
                    validator.Validate(gridCell.Value);
                }
                catch(Exception ex)
                {
                    AppendErrorText(gridCell, ex.Message);
                    isValid = false;
                }
            }

            return isValid;
        }

        /// <summary>
        /// Executes the configuration validator attributes.
        /// </summary>
        /// <param name="grid">The grid.</param>
        /// <param name="site">The site.</param>
        /// <returns></returns>
        public static bool ExecuteConfigurationValidatorAttributes(DataGridView grid, ISite site)
        {
            return ExecuteConfigurationValidatorAttributes(grid, site, new AutomaticBindingStrategy());
        }

        /// <summary>
        /// Checks the duplicates.
        /// </summary>
        /// <param name="grid">The grid.</param>
        /// <param name="column">The column.</param>
        /// <param name="errorMessage">The error message.</param>
        /// <returns></returns>
        public static bool CheckDuplicates(DataGridView grid, DataGridViewColumn column, string errorMessage)
        {
            return CheckDuplicates<object>(grid, column, errorMessage,
                delegate(DataGridViewRow row)
                {
                    return row.Cells[column.Index].Value;
                });
        }

        /// <summary>
        /// Checks the duplicates.
        /// </summary>
        /// <param name="grid">The grid.</param>
        /// <param name="column">The column.</param>
        /// <param name="errorMessage">The error message.</param>
        /// <param name="rowDataConverter">The row data converter.</param>
        /// <returns></returns>
        public static bool CheckDuplicates<T>(DataGridView grid, DataGridViewColumn column, string errorMessage, Converter<DataGridViewRow, T> rowDataConverter)
        {
            bool isValid = true;

            List<T> values = new List<T>();
            foreach(DataGridViewRow row in grid.Rows)
            {
                T rowValue = rowDataConverter(row);
                if(rowValue != null)
                {
                    if(values.Contains(rowValue))
                    {
                        AppendErrorText(row.Cells[column.Index], errorMessage);
                        isValid = false;
                    }
                    else
                    {
                        values.Add(rowValue);
                    }
                }
            }

            return isValid;
        }

        /// <summary>
        /// Appends the error text.
        /// </summary>
        /// <param name="cell">The cell.</param>
        /// <param name="message">The message.</param>
        public static void AppendErrorText(DataGridViewCell cell, string message)
        {
            if(cell == null)
                throw new ArgumentNullException("cell");

            if(string.IsNullOrEmpty(cell.ErrorText))
                cell.ErrorText = message;
            else
                cell.ErrorText += Environment.NewLine + message;
        }
    }
}
